Day 7 我們正式開始把 PowerShell 用在 Windows Server 的健康檢查。
目前已經可以取得:
CPU
Memory
Disk
例如:
SERVER01
CPU : 22% Normal
Memory : 68% Normal
Disk C: : 71% Normal
Disk D: : 91% Critical
這比單純執行:
Test-Connection
已經多了不少資訊。
但還是有一個問題:
CPU、Memory、Disk 都正常,就代表 Server 沒問題嗎?
不一定。
因為有可能:
CPU 20%
Memory 55%
Disk 60%
但 IIS 已經停止
也可能:
所有資源都正常
但 Server 10 分鐘前才剛重開機
甚至可能:
Server 已經連續運作 400 天
這些都是系統工程師巡檢時值得注意的資訊。
所以 Day 8 我們繼續增加三個項目:
Service
Last Boot Time
Uptime
最後把巡檢結果慢慢整理成:
SERVER01
CPU : Normal
Memory : Normal
Disk : Normal
Service : Warning
Uptime : 35 Days
Overall : Warning
為什麼 Service 一定要檢查?
Windows Server 上真正提供服務的,通常不是 Windows 本身,而是上面執行的各種 Service。
例如:
IIS
SQL Server
DNS
DHCP
Print Spooler
Windows Time
Backup Agent
Monitoring Agent
假設今天是一台 Web Server。
即使:
CPU 10%
Memory 40%
Disk 50%
但:
World Wide Web Publishing Service
已經停止。
對使用者來說,這台 Server 一樣是:
有問題。
所以 Server Health Check 不能只看硬體資源。
還要知道:
它應該提供的服務,現在有沒有正常運作。
先從 Get-Service 開始
前面幾天我們已經用過:
Get-Service
例如:
Get-Service |
Select-Object Name, DisplayName, Status
可以看到:
Status Name DisplayName
Running EventLog Windows Event Log
Running W32Time Windows Time
Stopped Spooler Print Spooler
如果只想查一個 Service:
Get-Service -Name W32Time
例如:
Status Name DisplayName
Running W32Time Windows Time
這已經可以拿來做最基本的 Service Check。
不要直接把所有 Stopped Service 當成異常
這裡非常重要。
如果我們寫:
Get-Service |
Where-Object Status -eq "Stopped"
通常會看到一大堆結果。
但這不代表 Server 有幾十個異常。
Windows 本身就存在很多:
Manual
Trigger Start
Disabled
按需求啟動
的 Service。
所以真正做維運時,我不建議單純:
找出所有 Stopped Service,然後全部標紅。
比較合理的做法是:
先定義這台 Server 上「必須運作」的 Service。
建立 Critical Service 清單
假設今天我們想監控:
Windows Event Log
Windows Time
Windows Management Instrumentation
對應名稱可能是:
$CriticalServices = @(
"EventLog"
"W32Time"
"Winmgmt"
)
這跟 Day 5 的 Server Array 完全一樣。
接著逐筆檢查:
foreach ($ServiceName in $CriticalServices) {
Get-Service -Name $ServiceName
}
PowerShell 就會依序檢查:
EventLog
W32Time
Winmgmt
判斷 Service 是否 Running
可以寫成:
foreach ($ServiceName in $CriticalServices) {
$Service = Get-Service `
-Name $ServiceName `
-ErrorAction SilentlyContinue
if ($Service.Status -eq "Running") {
Write-Host "$ServiceName : Running"
}
else {
Write-Host "$ServiceName : Stopped"
}
}
結果可能:
EventLog : Running
W32Time : Running
Winmgmt : Running
如果某個 Service 停止:
EventLog : Running
W32Time : Stopped
Winmgmt : Running
我們就馬上知道:
W32Time 需要進一步確認。
但如果 Service 根本不存在呢?
這又是實際環境會遇到的狀況。
例如:
Get-Service -Name MSSQLSERVER
如果這台 Server 根本沒有安裝 SQL Server,就會找不到。
所以 Service Check 最好至少分三種狀態:
Running
Stopped
NotFound
可以改成:
foreach ($ServiceName in $CriticalServices) {
$Service = Get-Service `
-Name $ServiceName `
-ErrorAction SilentlyContinue
if ($null -eq $Service) {
$Status = "NotFound"
}
elseif ($Service.Status -eq "Running") {
$Status = "Running"
}
else {
$Status = $Service.Status.ToString()
}
Write-Host "$ServiceName : $Status"
}
這樣就比較容易判斷:
EventLog Running
W32Time Stopped
MSSQLSERVER NotFound
把 Service 結果做成 Object
跟前幾天一樣,我們不要只停在 Write-Host。
建立:
$ServiceResults = @()
接著:
foreach ($ServiceName in $CriticalServices) {
$Service = Get-Service `
-Name $ServiceName `
-ErrorAction SilentlyContinue
if ($null -eq $Service) {
$Status = "NotFound"
}
else {
$Status = $Service.Status.ToString()
}
$Result = [PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
ServiceName = $ServiceName
Status = $Status
CheckTime = Get-Date
}
$ServiceResults += $Result
}
最後:
$ServiceResults
可能得到:
ComputerName ServiceName Status CheckTime
SERVER01 EventLog Running 2026/09/16 05:30
SERVER01 W32Time Stopped 2026/09/16 05:30
SERVER01 Winmgmt Running 2026/09/16 05:30
這就可以繼續:
Export-Csv
做成報表。
接著看 Server 最後一次什麼時候開機
這個資訊其實很好用。
PowerShell 可以透過:
Get-CimInstance Win32_OperatingSystem
取得。
前一天我們已經用它取得 Memory。
今天同一個 Object 裡,其實還有:
LastBootUpTime
可以執行:
Get-CimInstance Win32_OperatingSystem |
Select-Object LastBootUpTime
例如:
2026/08/15 上午 03:21:06
也可以:
$OS = Get-CimInstance Win32_OperatingSystem
$OS.LastBootUpTime
假設結果:
2026/08/15 03:21:06
這就是:
這台 Windows 最後一次開機的時間。
為什麼 Last Boot Time 值得放進巡檢?
有幾種情況很實用。
例如使用者說:
昨天 Server 好像有斷掉。
我們可以先查:
$OS.LastBootUpTime
如果結果是:
2026/09/15 02:13
至少可以知道:
這台 Server 最近確實曾經重新啟動。
另外有些 Server 可能因為:
Windows Update
Crash
Power Failure
人工 Restart
硬體問題
被重新啟動。
LastBootUpTime 可以當作後續排查的其中一個線索。
從 Last Boot Time 算 Uptime
既然知道:
現在時間
以及:
上次開機時間
兩者相減,就可以算:
Uptime。
例如:
$OS = Get-CimInstance Win32_OperatingSystem
$LastBootTime = $OS.LastBootUpTime
$Uptime = (Get-Date) - $LastBootTime
接著:
$Uptime
可能看到:
Days : 32
Hours : 4
Minutes : 21
Seconds : 18
Milliseconds : 236
Ticks : ...
TotalDays : 32.181...
TotalHours : ...
這又是一個 Object。
所以可以直接取得:
$Uptime.Days
例如:
32
代表:
Server 已經運作 32 天。
做成比較容易閱讀的格式
如果直接把整個 $Uptime 放進報表,內容會很多。
所以可以自己整理:
$UptimeText = "$($Uptime.Days) Days $($Uptime.Hours) Hours $($Uptime.Minutes) Minutes"
例如:
32 Days 4 Hours 21 Minutes
這就比較適合拿來看。
Uptime 越久越好嗎?
這裡也很容易有一個誤解:
Uptime 500 Days
看起來好像代表:
這台 Server 超級穩。
但未必。
Uptime 很久可能同時代表:
長期沒有維護
Patch 沒有套用
一直沒有 Reboot
Pending Restart
Windows Update 沒完成
所以:
Uptime 很長本身不是故障,但值得被注意。
相反地,如果:
Uptime = 15 Minutes
也不一定有問題。
可能只是剛完成:
正常維護
Windows Update
計畫性重開機
所以 Uptime 一樣屬於:
巡檢線索,而不是單獨的故障判定。
我們可以設定一個 Uptime 提醒
假設公司內部規範是:
超過 90 Days
→ Reminder
可以:
if ($Uptime.TotalDays -ge 90) {
$UptimeStatus = "Warning"
}
else {
$UptimeStatus = "Normal"
}
但這個 90 Days 只是範例。
實際環境還是要依:
公司的 Patch Policy
Windows Update Policy
維護窗口
服務需求
來決定。
另外也可以偵測「剛剛是不是重開過」
例如:
if ($Uptime.TotalHours -lt 1) {
$RecentReboot = $true
}
else {
$RecentReboot = $false
}
如果結果:
RecentReboot = True
就代表:
這台 Server 開機還不到一小時。
這在每日巡檢時也很實用。
例如本來沒有排定維護,但早上突然看到:
LastBootTime : 05:10
Uptime : 20 Minutes
那就值得去確認:
為什麼今天凌晨突然重開?
把 Uptime 做成 Object
可以整理:
$OS = Get-CimInstance Win32_OperatingSystem
$LastBootTime = $OS.LastBootUpTime
$Uptime = (Get-Date) - $LastBootTime
$BootInfo = [PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
LastBootTime = $LastBootTime
UptimeDays = $Uptime.Days
UptimeHours = $Uptime.Hours
UptimeText = "$($Uptime.Days) Days $($Uptime.Hours) Hours"
CheckTime = Get-Date
}
顯示:
ComputerName : SERVER01
LastBootTime : 2026/08/15 03:21:06
UptimeDays : 32
UptimeHours : 2
UptimeText : 32 Days 2 Hours
CheckTime : 2026/09/16 05:30:00
現在我們的 Server Check 已經又多了一塊。
Day 7 + Day 8 開始整合
目前已經有:
CPU
Memory
Disk
Service
Last Boot Time
Uptime
可以把它想成:
Windows Server
├── Performance
│ ├── CPU
│ └── Memory
│
├── Storage
│ └── Disk
│
├── Application / System
│ └── Service
│
└── System
├── Last Boot Time
└── Uptime
這才慢慢接近真正的 Server Health Check。
開始思考 Overall Status
假設今天:
CPU Normal
Memory Normal
Disk Normal
Service Normal
Uptime Normal
那很好判斷:
Overall = Healthy
但如果:
CPU Normal
Memory Normal
Disk Warning
Service Normal
Uptime Normal
那:
Overall = Warning
如果:
CPU Normal
Memory Normal
Disk Normal
Service Critical
Uptime Normal
整體應該:
Overall = Critical
先定義簡單規則
我們可以先使用:
Critical 優先
Warning 第二
全部正常才是 Healthy
也就是:
任何一項 Critical
↓
Overall = Critical
沒有 Critical
但有 Warning
↓
Overall = Warning
都沒有
↓
Overall = Healthy
這樣邏輯會比較清楚。
判斷 Critical Service
例如:
$StoppedServices = $ServiceResults |
Where-Object {
$_.Status -ne "Running"
}
接著:
if ($StoppedServices.Count -gt 0) {
$ServiceStatus = "Critical"
}
else {
$ServiceStatus = "Normal"
}
意思就是:
只要我們定義的 Critical Service 中,有任何一個不是 Running,就標記 Critical。
注意這裡之所以可以這樣做,是因為:
我們自己定義了 Critical Service 清單
而不是拿所有 Windows Service 去判斷。
Disk 也可以判斷整體狀態
假設 Day 7 已經有:
$DiskResults
其中每一筆都有:
Normal
Warning
Critical
那可以:
if ($DiskResults.Status -contains "Critical") {
$DiskOverallStatus = "Critical"
}
elseif ($DiskResults.Status -contains "Warning") {
$DiskOverallStatus = "Warning"
}
else {
$DiskOverallStatus = "Normal"
}
這裡第一次看到:
-contains
可以簡單理解為:
這組資料裡面有沒有這個值?
例如:
Normal
Normal
Critical
那:
$DiskResults.Status -contains "Critical"
就是:
True
最後再算 Overall Status
假設目前有:
$CPUStatus
$MemoryStatus
$DiskOverallStatus
$ServiceStatus
$UptimeStatus
可以先放進 Array:
$HealthStatus = @(
$CPUStatus
$MemoryStatus
$DiskOverallStatus
$ServiceStatus
$UptimeStatus
)
例如:
Normal
Normal
Warning
Normal
Normal
接著:
if ($HealthStatus -contains "Critical") {
$OverallStatus = "Critical"
}
elseif ($HealthStatus -contains "Warning") {
$OverallStatus = "Warning"
}
else {
$OverallStatus = "Healthy"
}
這個邏輯就比前一天一個一個寫:
if CPU...
if Memory...
if Disk...
容易擴充很多。
做出 Summary Object
最後可以:
$Summary = [PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
CheckTime = Get-Date
CPUUsage = $CPUUsage
CPUStatus = $CPUStatus
MemoryUsage = $MemoryUsage
MemoryStatus = $MemoryStatus
DiskStatus = $DiskOverallStatus
ServiceStatus = $ServiceStatus
LastBootTime = $LastBootTime
UptimeDays = [math]::Round($Uptime.TotalDays, 1)
UptimeStatus = $UptimeStatus
OverallStatus = $OverallStatus
}
結果就可能變成:
ComputerName : SERVER01
CheckTime : 2026/09/16 05:40
CPUUsage : 23
CPUStatus : Normal
MemoryUsage : 68
MemoryStatus : Normal
DiskStatus : Warning
ServiceStatus : Normal
LastBootTime : 2026/08/15 03:21
UptimeDays : 32.1
UptimeStatus : Normal
OverallStatus : Warning
現在第一眼就可以看到:
OverallStatus : Warning
接著再去看:
到底是哪一項 Warning?
發現:
DiskStatus : Warning
那工程師就可以優先往 Disk 去查。
今天整理一份完整 Script
把 Day 8 主要內容整理起來。
這一版先專注在:
Service
Uptime
Overall Status
CPU、Memory、Disk 可以接 Day 7 已經取得的結果。
$ComputerName = $env:COMPUTERNAME
$CheckTime = Get-Date
$OS = Get-CimInstance Win32_OperatingSystem
$LastBootTime = $OS.LastBootUpTime
$Uptime = (Get-Date) - $LastBootTime
if ($Uptime.TotalDays -ge 90) {
$UptimeStatus = "Warning"
}
else {
$UptimeStatus = "Normal"
}
$CriticalServices = @(
"EventLog"
"W32Time"
"Winmgmt"
)
$ServiceResults = @()
foreach ($ServiceName in $CriticalServices) {
$Service = Get-Service `
-Name $ServiceName `
-ErrorAction SilentlyContinue
if ($null -eq $Service) {
$Status = "NotFound"
}
else {
$Status = $Service.Status.ToString()
}
$ServiceResult = [PSCustomObject]@{
ComputerName = $ComputerName
ServiceName = $ServiceName
Status = $Status
CheckTime = $CheckTime
}
$ServiceResults += $ServiceResult
}
$ProblemServices = $ServiceResults |
Where-Object {
$_.Status -ne "Running"
}
if ($ProblemServices.Count -gt 0) {
$ServiceStatus = "Critical"
}
else {
$ServiceStatus = "Normal"
}
$Summary = [PSCustomObject]@{
ComputerName = $ComputerName
CheckTime = $CheckTime
LastBootTime = $LastBootTime
UptimeDays = [math]::Round(
$Uptime.TotalDays,
1
)
UptimeStatus = $UptimeStatus
ServiceStatus = $ServiceStatus
}
Write-Host ""
Write-Host "===== Server Health Check ====="
Write-Host ""
$Summary
Write-Host ""
Write-Host "===== Critical Services ====="
Write-Host ""
$ServiceResults
執行後可能看到:
===== Server Health Check =====
ComputerName : SERVER01
CheckTime : 2026/09/16 05:45
LastBootTime : 2026/08/15 03:21
UptimeDays : 32.1
UptimeStatus : Normal
ServiceStatus : Critical
===== Critical Services =====
ServiceName Status
EventLog Running
W32Time Stopped
Winmgmt Running
現在就很清楚了:
ServiceStatus = Critical
問題來源:
W32Time = Stopped
再輸出 CSV
延續前面的做法:
$ReportFolder = "C:\Temp"
$Date = Get-Date -Format "yyyyMMdd"
確認資料夾:
if (-not (Test-Path $ReportFolder)) {
New-Item `
-Path $ReportFolder `
-ItemType Directory |
Out-Null
}
Server Summary:
$Summary |
Export-Csv -Path "$ReportFolder\Server_Health_$Date.csv"
-NoTypeInformation `
-Encoding UTF8
Service:
$ServiceResults |
Export-Csv -Path "$ReportFolder\Service_Status_$Date.csv"
-NoTypeInformation `
-Encoding UTF8
最後:
C:\Temp
│
├── Server_Health_20260916.csv
└── Service_Status_20260916.csv
我們的巡檢資料又比 Day 7 更完整。
Critical Service 不應該每台 Server 都一樣
這也是今天我覺得很重要的一點。
例如:
Domain Controller
可能在意:
NTDS
DNS
Netlogon
KDC
DFSR
W32Time
IIS Server
可能在意:
W3SVC
WAS
SQL Server
可能在意:
MSSQLSERVER
SQLSERVERAGENT
所以未來不能永遠寫死:
$CriticalServices = @(
"EventLog"
"W32Time"
"Winmgmt"
)
更成熟的做法會是:
Server Role
↓
決定要檢查哪些 Service
例如:
DC01
→ NTDS / DNS / Netlogon / KDC
WEB01
→ W3SVC / WAS
SQL01
→ MSSQLSERVER / SQLSERVERAGENT
這個觀念之後做多台 Server 巡檢時會非常重要。
我們的 Health Check 已經開始有架構了
如果從 Day 1 回頭看,到現在已經不是單純:
Get-Service
而是慢慢變成:
Windows Server
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
CPU Memory Disk
│ │ │
└────────────┼────────────┘
│
Service
│
Uptime
│
▼
Health Status
│
┌─────────┴─────────┐
▼ ▼
Summary Detail
│ │
└─────────┬─────────┘
▼
CSV
這其實就是一個很基本的:
Windows Server Health Check Tool
Day 8 小結
今天我們沒有重新做一套新的東西。
而是在 Day 7 的:
CPU
Memory
Disk
上,再加入:
Service
Last Boot Time
Uptime
並開始建立:
Overall Status
其中幾個值得記住的指令與觀念:
Get-Service
Get-CimInstance Win32_OperatingSystem
$OS.LastBootUpTime
(Get-Date) - $LastBootTime
$Uptime.TotalDays
以及:
-contains
幫助我們判斷一組 Status 裡,有沒有:
Warning
Critical
但今天真正重要的還是:
不要只問「Server 有沒有活著」,而是要問「它應該提供的功能是否正常」。
所以健康檢查應該逐漸從:
Ping
進化成:
Connectivity
+
Resource
+
Storage
+
Service
+
System State
我們的巡檢 Script 到這裡,已經開始比較接近真正工作中可以使用的工具。
Day 9 預告
Day 9|Windows Event Log 自動巡檢:不要等使用者報錯才去翻事件檢視器
目前我們可以知道:
CPU 高不高?
Memory 夠不夠?
Disk 快滿了嗎?
Service 有沒有停止?
Server 什麼時候開機?
但真正遇到 Windows 問題時,我們通常還會打開:
Event Viewer
去找:
Error
Warning
Critical
所以 Day 9 我們會開始使用:
Get-WinEvent
把原本:
開 Event Viewer
↓
點 Windows Logs
↓
點 System
↓
Filter Current Log
↓
找 Error
改成:
PowerShell
↓
自動讀 Event Log
↓
篩選最近 24 小時
↓
找 Critical / Error
↓
整理 Event ID
↓
輸出報表
下一篇我們會讓這支 Health Check 開始具備一點真正的 異常排查能力。